unit wbBlobStream;

interface

uses
  SysUtils, Classes, DSServer, DSReflect, DSCommonServer, Datasnap.DSProxy,
  DBXCommon;

type

  TServerMethods1Client = class(TDSAdminClient)
  private
    FGetFileStreamCommand: TDBXCommand;
  public
    constructor Create(ADBXConnection: TDBXConnection); overload;
    constructor Create(ADBXConnection: TDBXConnection;
      AInstanceOwner: Boolean); overload;
    destructor Destroy; override;
    function GetFileStream: TStream;
  end;

{$METHODINFO ON}
  // include detailed RTTI
  TBase<T: class> = class(TComponent)
  strict protected
    procedure LogMessage(const Severity: TLogSeverity; const Msg: string);
  public
    // Server method to get the parameter type names
    procedure GetTypeNames(I: T; ADeclaredName, AActualName: TDBXStringValue);
  end;
{$METHODINFO OFF}

  TStreamFile = class(TBase<TStream>)
  public
    function GetFile(const FileName: string): TStream;
  end;

implementation

function TServerMethods1Client.GetFileStream: TStream;
begin
  if FGetFileStreamCommand = nil then
  begin
    FGetFileStreamCommand := FDBXConnection.CreateCommand;
    FGetFileStreamCommand.CommandType := TDBXCommandTypes.DSServerMethod;
    FGetFileStreamCommand.Text := 'TServerMethods1.GetFileStream';
    FGetFileStreamCommand.Prepare;
  end;
  //FGetFileStreamCommand.Parameters[0].Value.SetWideString(FilenameAtServer);
  FGetFileStreamCommand.ExecuteUpdate;
  Result := FGetFileStreamCommand.Parameters[0].Value.GetStream(FInstanceOwner);
end;

{ TBase<T> }

procedure TBase<T>.GetTypeNames(I: T;
  ADeclaredName, AActualName: TDBXStringValue);
begin
  ADeclaredName.SetString(T.ClassName);
  AActualName.SetString(I.ClassName);
end;

procedure TBase<T>.LogMessage(const Severity: TLogSeverity; const Msg: string);
begin
  // code to log message
end;

{ TStreamFile }

function TStreamFile.GetFile(const FileName: string): TStream;
begin
  Result := TFileStream.Create(FileName, fmOpenRead);
  TFileStream(Result).Seek(0, TSeekOrigin.soBeginning);
end;

// The client includes The code:

procedure CopyStreamToMemoryStream(const ASource: TStream;
  var ADest: TMemoryStream; const Rewind: Boolean = True);
const
  LBufSize = $F000;
var
  LBuffer: PByte;
  LReadCount: Integer;
begin
  GetMem(LBuffer, LBufSize);
  ADest.Clear;
  try
    repeat
      LReadCount := ASource.Read(LBuffer^, LBufSize);
      if LReadCount > 0 then
        ADest.WriteBuffer(LBuffer^, LReadCount);
    until LReadCount < LBufSize;
  finally
    FreeMem(LBuffer, LBufSize);
  end;
  if Rewind then
    ADest.Seek(0, TSeekOrigin.soBeginning);
end;

procedure Test;
var
  ms: TMemoryStream;
  before: TDateTime;
  after: TDateTime;
  seconds: Extended;
begin
  ms := TMemoryStream.Create;

  before := Now;
  // let ProxyMethods do its own cleanup -- pass True as 2nd param
  with ProxyMethods.TPackageClient.Create(sqlcon.DBXConnection, True) do
    CopyStreamToMemoryStream(GetFile('c:\test.bin'), ms);
  after := Now;

  ms.SaveToFile('d:\test.bin');
  FreeAndNil(ms);

  seconds := MilliSecondsBetween(after, before) / 1000;
  ShowMessage(Format('Processing time: %f seconds', [seconds]));
end;
